/*
 * JGO2RDF creates a RDF representation of GAF2 files, which are automatically sourced from GO ftp site.
 * Copyright (C) 2012 Matthew M. Hindle & Artem Lysenko
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package uk.co.jgo2rdf.goa.goa2rdf.deligates;

import com.google.common.io.Files;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import org.apache.log4j.Logger;
import uk.co.jgo2rdf.exception.AccessionNonConformentException;
import uk.co.jgo2rdf.goa.deligates.EntityRDFizer;
import uk.co.jgo2rdf.goa.deligates.RefSeqRDFizer;
import uk.co.jgo2rdf.goa.deligates.TAIRRDFizer;
import uk.co.jgo2rdf.goa.deligates.UniProtRDFizer;
import uk.co.jgo2rdf.goa.go.GOFactory;
import uk.co.jgo2rdf.goa.model.GOAEntry;
import uk.co.jgo2rdf.rdf.lsrn.LSRNVocab;
import uk.co.jgo2rdf.rdf.refseq.URIs;
import uk.co.jgo2rdf.rdf.sio.SIOVocab;
import uk.co.jgo2rdf.tairidmapping.TAIRModel2uniprot;
import uk.co.jgo2rdf.tairidmapping.TAIRNCBIIDMapping;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 * User: mhindle
 * Date: 14/02/12
 * Time: 16:49
 * To change this template use File | Settings | File Templates.
 */
public class TAIRGeneGOAParser implements GOARDFizer {

    private static final Logger log = Logger.getLogger(TAIRGeneGOAParser.class);

    private boolean parseAGIAccessions;

    private UniProtRDFizer uniProtRDFizer = new UniProtRDFizer();
    private RefSeqRDFizer refSeqRDFizer = new RefSeqRDFizer();

    private TAIRModel2uniprot uniprot_index;
    private TAIRNCBIIDMapping ncbi_index;

    public TAIRGeneGOAParser(boolean parseAGIAccessions) throws IOException {
        this.parseAGIAccessions = parseAGIAccessions;
        File tempDir = Files.createTempDir();
        tempDir.deleteOnExit();
        this.uniprot_index = new TAIRModel2uniprot(tempDir.getAbsolutePath(), false);
        uniprot_index.parse();
        File tempDir2 = Files.createTempDir();
        tempDir2.deleteOnExit();
        this.ncbi_index = new TAIRNCBIIDMapping(tempDir2.getAbsolutePath(), false);
        ncbi_index.parse();
    }


    private Map<String, EntityRDFizer> rdfizers = new HashMap<String, EntityRDFizer>();

    @Override
    public void addEntityRDFizer(String database, EntityRDFizer rdfizer) {
        rdfizers.put(database.toUpperCase(), rdfizer);
    }

    private static final Pattern tairGeneP = Pattern.compile("(TAIR:){0,1}GENE:\\d+");

    protected static boolean isTAIRGene(String accession) {
        accession = accession.trim().toUpperCase();
        return accession.length() != 0
                && tairGeneP.matcher(accession).matches();
    }

    protected static String getTAIRGene(String geneProductFormID) {
        geneProductFormID = geneProductFormID.trim().toUpperCase();
        int index = geneProductFormID.indexOf("TAIR:");
        if (index > -1) {
            return geneProductFormID.substring((index) + "TAIR:".length(),
                    geneProductFormID.length());
        }
        return geneProductFormID;
    }


    @Override
    public void rdfizeGOA(Model jenaModel, GOAEntry goEntry, String provider) throws Exception {

        //if its a not we don't model it at the moment...availible on request
        if (goEntry.hasQualifier()
                && goEntry.getQualifier().toUpperCase().contains("NOT"))
            return;

        String type = goEntry.getDBObjectType();
        boolean subjectRNA = type.toUpperCase().contains("RNA");

        String database = goEntry.getDB();

        String dnaId = goEntry.getDBObject_ID();
        String rnaId = dnaId;

        EntityRDFizer rdfizer = rdfizers.get(database.trim().toUpperCase());

        Resource dna = rdfizer.rdfize(jenaModel, dnaId);
        Resource rna = dna;

        if (isTAIRGene(goEntry.getGeneProductFormID())) {
            rnaId = getTAIRGene(goEntry.getGeneProductFormID());
            if (!rnaId.equals(dnaId)) {
                rna = rdfizer.rdfize(jenaModel, rnaId);
            }
        }

        if (rdfizers == null) {
            throw new AccessionNonConformentException("Could not rdfize " + database + " database not recognised");
        }

        //if dna is not dna...make it so
        if (dna == null ||
                !dna.hasProperty(RDF.type) ||
                !dna.getProperty(RDF.type).equals(SIOVocab.dna)) {
            dna = jenaModel.createResource(URIs.TAIR_ACCESSION + dnaId + "-dna", SIOVocab.dna);
            dna.addLiteral(RDFS.label, dnaId + "-dna");
        }

        //if rna is not... rna make it so
        if (rna == null ||
                !rna.hasProperty(RDF.type) ||
                !rna.getProperty(RDF.type).equals(SIOVocab.rna)) {
            rna = jenaModel.createResource(URIs.TAIR_ACCESSION + rnaId + "-rna", SIOVocab.rna);
            rna.addLiteral(RDFS.label, rnaId + "-rna");
        }

        dna.addProperty(SIOVocab.is_transcribed_into, rna);

        Set<Resource> proteins = new HashSet<Resource>();
        Set<Resource> rnas = new HashSet<Resource>();
        rnas.add(rna);

        if (parseAGIAccessions) {
            Set<String> agiIds = extractAGIsFromSynonyms(goEntry.getDBObjectSynonym());

            String agiAccession = goEntry.getDBObjectName();
            if (agiAccession.trim().length() > 1
                    && TAIRRDFizer.isAGILocusId(agiAccession)) {
                agiIds.add(agiAccession);
            }

            String agiAccession2 = goEntry.getDBObjectSymbol();
            if (agiAccession2.trim().length() > 1
                    && TAIRRDFizer.isAGILocusId(agiAccession2)) {
                agiIds.add(agiAccession2);
            }

            //also attach the Arabidopsis Gene Identifers to the rna model
            removeDuplicateAGI(agiIds);
            for (String agiId : agiIds) {
                String locus = agiId;
                String gene = null;

                //check to see if this is a gene or a model
                int modelNo = agiId.lastIndexOf(".");
                if (modelNo > -1) {
                    gene = agiId;
                    locus = agiId.substring(0, modelNo);
                }

                Resource recordLocus = jenaModel.createResource(URIs.TAIR_LOCUS + locus, LSRNVocab.AGI_LocusCode_Record);
                recordLocus.addLiteral(RDFS.label, locus);
                Resource identifierLocus = jenaModel.createResource(LSRNVocab.AGI_LocusCode_Identifier);
                identifierLocus.addLiteral(SIOVocab.has_value, locus);
                identifierLocus.addLiteral(RDFS.label, locus + "-id");
                recordLocus.addProperty(SIOVocab.is_about, dna);

                if (gene != null) {
                    Resource recordGene = jenaModel.createResource(URIs.TAIR_LOCUS + gene, LSRNVocab.AGI_GeneModel_Record);
                    recordGene.addLiteral(RDFS.label, gene);
                    Resource identifierGene = jenaModel.createResource(LSRNVocab.AGI_GeneModel_Identifier);
                    identifierGene.addLiteral(SIOVocab.has_value, gene);
                    identifierGene.addLiteral(RDFS.label, gene + "-id");
                    recordGene.addProperty(SIOVocab.is_about, rna);

                    Set<String> uniprots = uniprot_index.getUniprotAccessions(gene);
                    for(String uniprot : uniprots) {
                        Resource protein = uniProtRDFizer.rdfize(jenaModel, uniprot);
                        rna.addProperty(SIOVocab.is_translated_into, protein);
                        proteins.add(protein);
                    }

                    Set<String> ncbiProteins = ncbi_index.getRefSeqProteinAccessions(gene);
                    for(String ncbiProtein: ncbiProteins) {
                        Resource  protein = refSeqRDFizer.rdfize(jenaModel, ncbiProtein);
                        rna.addProperty(SIOVocab.is_translated_into, protein);
                        proteins.add(protein);
                    }

                    Set<String> ncbiRNAs = ncbi_index.getRefSeqRNAAccessions(gene);
                    for(String ncbiRNA: ncbiRNAs) {
                        Resource ncbiRna = refSeqRDFizer.rdfize(jenaModel, ncbiRNA);
                        rnas.add(ncbiRna);
                        dna.addProperty(SIOVocab.is_transcribed_into, ncbiRna);
                    }
                }
            }
        }

        if (proteins.size() ==0 && !subjectRNA) {
            //create a protein...this is usually the subject of the function
            Resource protein = jenaModel.createResource(URIs.TAIR_ACCESSION + rnaId + "-protein", SIOVocab.protein);
            protein.addLiteral(RDFS.label, rna + "-protein");
            proteins.add(protein);
            rna.addProperty(SIOVocab.is_translated_into, protein);
        }

        String goID = goEntry.getGO_ID_String().toUpperCase();

        Resource entity = jenaModel.createResource(jenaModel.createResource(GOFactory.getGoUri(goID) + "_goa"));

        Resource go_record = jenaModel.createResource(GOFactory.getGoUri(goID), LSRNVocab.GO_Record);
        Resource go_identifier = jenaModel.createResource(GOFactory.getGoUri(goID) + "-identifier");
        go_identifier.addLiteral(SIOVocab.has_value, goID);
        go_record.addProperty(SIOVocab.has_unique_identifier_attribute, go_identifier);
        go_record.addLiteral(RDFS.label, goID);
        go_record.addProperty(OWL.sameAs, jenaModel.createResource(GOFactory.getOldGoUri(goID), LSRNVocab.GO_Record));

        entity.addProperty(SIOVocab.is_subject_of, go_record);

        //evidence class
        Resource ecURI = jenaModel.createResource(
                "http://purl.org/obo/owl/GO#GO_" + goEntry.getEvidenceCode().trim());

        Resource evidence = jenaModel.createResource(ecURI);
        evidence.addLiteral(RDFS.label, goEntry.getEvidenceCode());
        evidence.addLiteral(SIOVocab.has_provider, provider);

        entity.addProperty(SIOVocab.has_supporting_evidence, evidence);
        log.debug("Added " + goEntry.getEvidenceCode() + " as evidence to " + entity.getURI());

        if (subjectRNA)
            for (Resource rnaA: rnas)
                rnaA.addProperty(SIOVocab.is_subject_of, entity);
        else
            for(Resource protein :  proteins)
                protein.addProperty(SIOVocab.is_subject_of, entity);
    }

    private static Pattern pipeMatch = Pattern.compile("\\|");

    /**
     * Splits based on the pipe symbol (|) and then returns the AGI accessions as a Set
     *
     * @param synonymField the raw field from the entry
     * @return AGI symbols if any which were found
     */
    protected static Set<String> extractAGIsFromSynonyms(String synonymField) {
        Set<String> agiIds = new HashSet<String>(2);
        for (String geneId : pipeMatch.split(synonymField)) {
            if (geneId.trim().length() > 1
                    && TAIRRDFizer.isAGILocusId(geneId)) {
                agiIds.add(geneId);
            }
        }
        return agiIds;
    }


    /**
     * Removes AGI loci where a model for that loci exists
     */
    protected static void removeDuplicateAGI(Set<String> agis) {
        for (String loci : agis.toArray(new String[agis.size()])) {
            int modelNo = loci.lastIndexOf(".");
            if (modelNo > -1) {
                String locus = loci.substring(0, modelNo);
                agis.remove(locus);
            }
        }
    }


}
